/**
 * @file oled.h
 * @brief 波特律动OLED驱动(SSD1306)
 * @anchor 波特律动(keysking 博哥在学习)
 * @version 1.0
 * @date 2023-08-19
 * @license MIT License
 *
 * @attention
 * 本驱动库针对波特律动·keysking的STM32教程学习套件进行开发
 * 在其他平台或驱动芯片上使用可能需要进行移植
 *
 * @note
 * 使用流程:
 * 1. STM32初始化IIC完成后调用OLED.Init()初始化OLED. 注意STM32启动比OLED上电快, 可等待20ms再初始化OLED
 * 2. 调用OLED.NewFrame()开始绘制新的一帧
 * 3. 调用OLED.DrawXXX()系列函数绘制图形到显存 调用OLED.Printxxx()系列函数绘制文本到显存
 * 4. 调用OLED.ShowFrame()将显存内容显示到OLED
 *
 * @note
 * 为保证中文显示正常 请将编译器的字符集设置为UTF-8
 *
 */

#pragma once

#include "font.h"
#include "main.h"
#include "string.h"
#include "i2c.h"



enum OLED_ColorMode{
  NORMAL = 0x00, // 正常模式 黑底白字
  REVERSED = 0x01   // 反色模式 白底黑字
};

// OLED参数，根据大小修改

#define OLED_PAGE 4            // OLED页数
#define OLED_ROW 8 * OLED_PAGE // OLED行数
#define OLED_COLUMN 128        // OLED列数

/**
 * @brief OLED相关驱动等
 * 
 */
class OLED
{
private:
  /**
   * @brief 使用的I2C句柄
   * @note 使用其它IIC口请修改
   */
  static I2C_HandleTypeDef& IIC_Handle;
  /**
   * @brief IIC初始化函数
   * @note 使用其它IIC口请修改
   */
  static void (*IIC_Init)(void);

  static uint8_t ADDRESS;// OLED器件地址


// ========================== 底层通信函数 ==========================
private:
  /**
   * @brief 向OLED发送数据的函数
   * @param data 要发送的数据
   * @param len 要发送的数据长度
   * @return None
   * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他平台时应根据实际情况修改此函数
   */
  static void Send(uint8_t *data, uint8_t len);

private:
  /**
   * @brief 向OLED发送指令
   */
  static void SendCmd(uint8_t cmd);

private:
  /**
   * @brief 设置颜色模式 黑底白字或白底黑字
   * @param ColorMode 颜色模式COLOR_NORMAL/COLOR_REVERSED
   * @note 此函数直接设置屏幕的颜色模式
   */
  static void SetColorMode(OLED_ColorMode mode);

private:
  /**
   * @brief 设置显存中一字节数据的某几位
   * @param page 页地址
   * @param column 列地址
   * @param data 数据
   * @param start 起始位
   * @param end 结束位
   * @param color 颜色
   * @note 此函数将显存中的某一字节的第start位到第end位设置为与data相同
   * @note start和end的范围为0-7, start必须小于等于end
   * @note 此函数与OLED_SetByte_Fine的区别在于此函数只能设置显存中的某一真实字节
   */
  static void SetByte_Fine(uint8_t page, uint8_t column, uint8_t data, uint8_t start, uint8_t end, OLED_ColorMode color);

private:
  /**
   * @brief 设置显存中的一字节数据
   * @param page 页地址
   * @param column 列地址
   * @param data 数据
   * @param color 颜色
   * @note 此函数将显存中的某一字节设置为data的值
   */
  static void SetByte(uint8_t page, uint8_t column, uint8_t data, OLED_ColorMode color);

private:
  /**
   * @brief 设置显存中的一字节数据的某几位
   * @param x 横坐标
   * @param y 纵坐标
   * @param data 数据
   * @param len 位数
   * @param color 颜色
   * @note 此函数将显存中从(x,y)开始向下数len位设置为与data相同
   * @note len的范围为1-8
   * @note 此函数与OLED_SetByte_Fine的区别在于此函数的横坐标和纵坐标是以像素为单位的, 可能出现跨两个真实字节的情况(跨页)
   */
  static void SetBits_Fine(uint8_t x, uint8_t y, uint8_t data, uint8_t len, OLED_ColorMode color);

private:
  /**
   * @brief 设置显存中一字节长度的数据
   * @param x 横坐标
   * @param y 纵坐标
   * @param data 数据
   * @param color 颜色
   * @note 此函数将显存中从(x,y)开始向下数8位设置为与data相同
   * @note 此函数与OLED_SetByte的区别在于此函数的横坐标和纵坐标是以像素为单位的, 可能出现跨两个真实字节的情况(跨页)
   */
  static void SetBits(uint8_t x, uint8_t y, uint8_t data, OLED_ColorMode color);

private:
  /**
   * @brief 设置一块显存区域
   * @param x 起始横坐标
   * @param y 起始纵坐标
   * @param data 数据的起始地址
   * @param w 宽度
   * @param h 高度
   * @param color 颜色
   * @note 此函数将显存中从(x,y)开始的w*h个像素设置为data中的数据
   * @note data的数据应该采用列行式排列
   */
  static void SetBlock(uint8_t x, uint8_t y, const uint8_t *data, uint8_t w, uint8_t h, OLED_ColorMode color);

private:
  /**
   * @brief 获取UTF-8编码的字符长度
   */
  static uint8_t getUTF8Len(char *string);

// ========================== OLED驱动函数 ==========================
public:
  /**
   * @brief 初始化OLED (SSD1306)
   * @note 此函数是移植本驱动时的重要函数；
   * 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数；
   * 详见对应说明书
   */
  static void Init();

public:
  /**
   * @brief 复位器件
   * @note 拉低RES引脚200ms
   */
  static void Reset(void);

public:
  /**
   * @brief 开启OLED显示
   */
  static void DisPlay_On();

public:
  /**
   * @brief 关闭OLED显示
   */
  static void DisPlay_Off();

  // ========================== 显示显存内容 ==========================

public:
  /**
   * @brief 将当前显存显示到屏幕上
   * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数
   */
  static void ShowFrame();

  // ========================== 显存操作函数 ==========================
private:
  static uint8_t GRAM[OLED_PAGE][OLED_COLUMN];// 显存

public:
  /**
   * @brief 清空显存 
   * @note 就是数组都置零
   */
  static void ClearVideoMemory();

public:
  /**
   * @brief 设置一个像素点
   * @param x 横坐标
   * @param y 纵坐标
   * @param color 颜色
   */
  static void SetPixel(uint8_t x, uint8_t y, OLED_ColorMode color);


  // ========================== 图形绘制函数 ==========================

public:
  /**
   * @brief 绘制一条线段
   * @param x1 起始点横坐标
   * @param y1 起始点纵坐标
   * @param x2 终止点横坐标
   * @param y2 终止点纵坐标
   * @param color 颜色
   * @note 此函数使用Bresenham算法绘制线段
   */
  static void DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个矩形
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param w 矩形宽度
   * @param h 矩形高度
   * @param color 颜色
   */
  static void DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个填充矩形
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param w 矩形宽度
   * @param h 矩形高度
   * @param color 颜色
   */
  static void DrawFilledRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个三角形
   * @param x1 第一个点横坐标
   * @param y1 第一个点纵坐标
   * @param x2 第二个点横坐标
   * @param y2 第二个点纵坐标
   * @param x3 第三个点横坐标
   * @param y3 第三个点纵坐标
   * @param color 颜色
   */
  static void DrawTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个填充三角形
   * @param x1 第一个点横坐标
   * @param y1 第一个点纵坐标
   * @param x2 第二个点横坐标
   * @param y2 第二个点纵坐标
   * @param x3 第三个点横坐标
   * @param y3 第三个点纵坐标
   * @param color 颜色
   */
  static void DrawFilledTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个圆
   * @param x 圆心横坐标
   * @param y 圆心纵坐标
   * @param r 圆半径
   * @param color 颜色
   * @note 此函数使用Bresenham算法绘制圆
   */
  static void DrawCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个填充圆
   * @param x 圆心横坐标
   * @param y 圆心纵坐标
   * @param r 圆半径
   * @param color 颜色
   * @note 此函数使用Bresenham算法绘制圆
   */
  static void DrawFilledCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个椭圆
   * @param x 椭圆中心横坐标
   * @param y 椭圆中心纵坐标
   * @param a 椭圆长轴
   * @param b 椭圆短轴
   */
  static void DrawEllipse(uint8_t x, uint8_t y, uint8_t a, uint8_t b, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一张图片
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param img 图片
   * @param color 颜色
   */
  static void DrawImage(uint8_t x, uint8_t y, const Image *img, OLED_ColorMode color);


// ================================ 文字绘制 ================================
public:
  /**
   * @brief 绘制一个ASCII字符
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param ch 字符
   * @param font 字体
   * @param color 颜色
   */
  static void PrintASCIIChar(uint8_t x, uint8_t y, char ch, const ASCIIFont *font, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个ASCII字符串
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param str 字符串
   * @param font 字体
   * @param color 颜色
   */
  static void PrintASCIIString(uint8_t x, uint8_t y, const char *str, const ASCIIFont *font, OLED_ColorMode color);

public:
  /**
   * @brief 绘制一个ASCII字符数组
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param arr 字符数组起始地址
   * @param length 数组长度
   * @param font 字体
   * @param color 颜色
   */
  static void PrintASCIIArray(uint8_t x, uint8_t y, const char *arr, uint8_t length, const ASCIIFont *font, OLED_ColorMode color);

public:
  /**
   * @brief 绘制字符串
   * @param x 起始点横坐标
   * @param y 起始点纵坐标
   * @param str 字符串
   * @param font 字体
   * @param color 颜色
   *
   * @note 为保证字符串中的中文会被自动识别并绘制, 需:
   * 1. 编译器字符集设置为UTF-8
   * 2. 使用波特律动LED取模工具生成字模(https://led.baud-dance.com)
   */
  static void PrintString(uint8_t x, uint8_t y, char *str, const Font *font, OLED_ColorMode color);
};


